
在前一篇文章中,我們學習了如何開發能與外部世界互動的自定義工具。今天我們將探索一個新領域:讓 AI 具備「視覺」能力。Koog 框架提供了強大的圖像處理功能,能夠讓我們的 AI Agent 理解和分析各種視覺內容,從簡單的圖片描述到複雜的文字提取,開啟無限的應用可能
在現代數位世界中,視覺資訊無處不在。從社群媒體的照片、工作文件的截圖,到產品目錄的圖片,我們每天都在處理大量的視覺內容。傳統的 AI 只能處理文字,但現在的 AI 已經能夠「看見」並理解圖像,這為我們帶來了全新的應用場景
讓我們從最基本的圖像分析開始,建立一個簡單易用的 ImageAnalyzer
class ImageAnalyzer(private val client: OpenAILLMClient) {
    /**
     * 判斷路徑是否為 URL
     */
    private fun isURL(path: String): Boolean {
        return path.startsWith("http://") || path.startsWith("https://")
    }
    /**
     * 基本圖像描述功能
     * 接收圖片路徑或 URL,返回詳細的圖像描述
     */
    suspend fun describeImage(
        imagePath: String,
        detailLevel: String = "詳細"
    ): String {
        val response = client.execute(
            prompt = prompt("multimodel") {
                user {
                    text("請${detailLevel}地描述這張圖片的內容,包括:")
                    text("- 主要物件和人物")
                    text("- 場景和背景")
                    text("- 色彩和構圖")
                    text("- 整體氛圍和感受")
                    // 這是 Koog 的核心語法:直接在 prompt 中加入圖片
                    attachments {
                        if (isURL(imagePath)) {
                            image(imagePath) // 網路 URL 直接使用
                        } else {
                            image(Path(imagePath)) // 本地檔案路徑需使用 Path
                        }
                    }
                }
            },
            // 使用支援視覺的模型
            model = OpenAIModels.CostOptimized.GPT4_1Mini
        )
        return response.first().content
    }
    /**
     * 圖像文字提取功能(OCR)
     * 從圖片中準確提取所有文字內容
     */
    suspend fun extractText(imagePath: String): String {
        val response = client.execute(
            prompt = prompt("multimodel") {
                user {
                    text("請準確提取這張圖片中的所有文字內容。")
                    text("要求:")
                    text("1. 保持原有的格式和排版")
                    text("2. 區分不同的文字區塊")
                    text("3. 如果是表格,請用適當的格式呈現")
                    text("4. 如果看不清楚某些文字,請標註 [不清楚]")
                    // 這是 Koog 的核心語法:直接在 prompt 中加入圖片
                    attachments {
                        if (isURL(imagePath)) {
                            image(imagePath) // 網路 URL 直接使用
                        } else {
                            image(Path(imagePath)) // 本地檔案路徑需使用 Path
                        }
                    }
                }
            },
            model = OpenAIModels.CostOptimized.GPT4_1Mini
        )
        return response.first().content
    }
}
讓我們看看如何在實際應用中使用 ImageAnalyzer
suspend fun main() {
    // 建立 OpenAI 客戶端和圖像分析器
     val client = OpenAILLMClient(ApiKeyManager.openAIApiKey!!)
     val analyzer = ImageAnalyzer.ImageAnalyzer(client)
     println("=== Koog 圖像處理範例 ===\n")
     // 範例 1:描述一張風景照片
     println("1. 圖像內容描述")
     val description = analyzer.describeImage(
         imagePath = "https://images.pexels.com/photos/1172064/pexels-photo-1172064.jpeg",
         detailLevel = "簡潔"
     )
     println("圖片描述:$description\n")
     // 範例 2:從截圖中提取文字(如程式碼截圖)
     println("2. 文字提取(OCR)")
     val extractedText = analyzer.extractText(
         imagePath = "/Users/cash/Downloads/ocr.png"
     )
     println("提取的文字:\n$extractedText\n")
}
圖片內容描述,我使用的是這張圖片

文字提取(OCR),我使用的是這張

=== Koog 圖像處理範例 ===
1. 圖像內容描述
圖片描述:- 主要物件和人物:圖片中央有一棟木造小屋,周圍環繞著多棵樹木,沒有出現人物。
- 場景和背景:小屋坐落於綠意盎然的山坡上,背景是一片茂密的森林。
- 色彩和構圖:以豐富的綠色為主調,層次分明,畫面上方為深綠色森林,下方為明亮的草地,整體構圖平衡且有深度。
- 整體氛圍和感受:給人寧靜、悠閒且貼近自然的感覺,彷彿是遠離都市喧囂的世外桃源。
2. 文字提取(OCR)
提取的文字:
// 建立 OpenAI 客戶端和圖像分析器
val client = OpenAIILMClient(ApiKeyManager.openAIApiKey!!)
val analyzer = ImageAnalyzer.ImageAnalyzer(client)
println("=== Koog 圖像處理範例 ===\n")
除了前面介紹的簡化語法,Koog 還提供了更靈活的 Attachment API,讓我們能夠更精確地控制圖像處理過程。這種方式特別適合需要處理不同圖像來源或需要更多控制參數的場景
Koog 提供了多種內容來源類型,讓我們能夠靈活處理不同的圖像來源
// 直接從網路 URL 載入圖片
AttachmentContent.URL("https://www.pexels.com/zh-tw/photo/1172064/")
// 從本地檔案讀取為 byte array
val imageBytes = Files.readAllBytes(Path("/path/to/image.jpg"))
AttachmentContent.Binary.Bytes(imageBytes)
// 處理 Base64 編碼的圖片資料
AttachmentContent.Binary.Base64("iVBORw0KGgoAAAANSUhEUgAA...")
class AdvancedImageAnalyzer(private val client: OpenAILLMClient) {
    /**
     * 使用 Attachment API 的進階圖像描述
     * 支援更多參數控制和圖像來源類型
     */
    suspend fun describeImageAdvanced(
        imagePath: String,
        fileName: String? = null,
        format: String = "jpg"
    ): String {
        val response = client.execute(
            prompt = prompt("multimodel"){
                user(
                    content = "請詳細描述這張圖片的內容",
                    attachments = listOf(
                        when {
                            imagePath.startsWith("http") -> {
                                // 網路 URL 圖片
                                Attachment.Image(
                                    content = AttachmentContent.URL(imagePath),
                                    format = format,
                                    fileName = fileName ?: "network_image.$format"
                                )
                            }
                            else -> {
                                // 本地檔案,讀取為 byte array
                                val imageBytes = Files.readAllBytes(Path(imagePath))
                                Attachment.Image(
                                    content = AttachmentContent.Binary.Bytes(imageBytes),
                                    format = format,
                                    fileName = fileName ?: Path(imagePath).fileName.toString()
                                )
                            }
                        }
                    )
                )
            },
            model = OpenAIModels.CostOptimized.GPT4_1Mini
        )
        return response.first().content
    }
    /**
     * 批次處理多張圖片
     */
    suspend fun batchImageAnalysis(
        imagePaths: List<String>,
        prompt: String = "比較這些圖片的異同點"
    ): String {
        val attachments = imagePaths.mapIndexed { index, path ->
            when {
                path.startsWith("http") -> {
                    Attachment.Image(
                        content = AttachmentContent.URL(path),
                        format = getImageFormat(path),
                        fileName = "image_$index.${getImageFormat(path)}"
                    )
                }
                else -> {
                    val imageBytes = Files.readAllBytes(Path(path))
                    Attachment.Image(
                        content = AttachmentContent.Binary.Bytes(imageBytes),
                        format = getImageFormat(path),
                        fileName = Path(path).fileName.toString()
                    )
                }
            }
        }
        val response = client.execute(
            prompt = prompt("multimodel") {
                user(
                    content = prompt,
                    attachments = attachments
                )
            },
            model = OpenAIModels.CostOptimized.GPT4_1Mini
        )
        return response.first().content
    }
    private fun getImageFormat(path: String): String {
        return path.substringAfterLast(".", "jpg").lowercase()
    }
}
suspend fun main() {
    val client = OpenAILLMClient(ApiKeyManager.openAIApiKey!!)
    val analyzer = AdvancedImageAnalyzer(client)
    // 範例 1:處理網路圖片
    println("=== 網路圖片分析 ===")
    val urlResult = analyzer.describeImageAdvanced(
        imagePath = "https://images.pexels.com/photos/1172064/pexels-photo-1172064.jpeg",
        fileName = "landscape.jpg",
        format = "jpg"
    )
    println(urlResult)
    // 範例 2:處理本地檔案
    println("\n=== 本地檔案分析 ===")
    val localResult = analyzer.describeImageAdvanced(
        imagePath = "/Users/cash/Downloads/ocr.png",
        format = "png"
    )
    println(localResult)
    // 範例 3:批次處理多張圖片
    println("\n=== 批次圖片比較 ===")
    val imagePaths = listOf(
        "https://images.pexels.com/photos/1172064/pexels-photo-1172064.jpeg",
        "/Users/cash/Downloads/ocr.png"
    )
    val batchResult = analyzer.batchImageAnalysis(
        imagePaths = imagePaths,
        prompt = "比較這兩張圖片的風格、色調和主題"
    )
    println(batchResult)
}
這裡使用的圖片,和前面的一樣,就不在列出
=== 網路圖片分析 ===
這張圖片展示了一間位於山坡上的木屋,周圍環繞著濃密的綠色植被。山坡綠草如茵,分布著零星的小樹和灌木。木屋呈現傳統設計,屋頂是深色的,窗戶白色框架,顯得古樸而安靜。背景是一片茂密的森林,高大的針葉樹和闊葉樹錯落有致,形成濃密的綠蔭,給人一種幽靜、自然且遠離城市喧囂的感覺。整體畫面清新靜謐,表達出與大自然和諧共處的意境。
=== 本地檔案分析 ===
這張圖片內容是一段以 Kotlin 語言撰寫的程式碼,展示了如何建立 OpenAI 客戶端和圖像分析器。程式碼中包含以下部分:
1. 註解(以 `//` 開頭):
   - 說明程式碼的用途:「建立 OpenAI 客戶端和圖像分析器」
2. 第一行程式碼:
   val client = OpenAI LLMClient(ApiKeyManager.openAIApiKey!!)
   - 使用 `val` 宣告一個常數 `client`
   - 透過 `OpenAILLMClient` 類別來建立客戶端物件,並傳入 API 金鑰(由 `ApiKeyManager.openAIApiKey!!` 提供)
   - 其中 `!!` 表示強制解包,確保 API 金鑰不是 null
3. 第二行程式碼:
   val analyzer = ImageAnalyzer.ImageAnalyzer(client)
   - 建立圖像分析器物件,名稱為 `analyzer`
   - 使用 `ImageAnalyzer.ImageAnalyzer` 類別,並將剛剛建立的 `client` 傳入建構子
4. 第三行程式碼:
   println("=== Koog 圖像處理範例 ===\n")
   - 輸出一行文字提示:「=== Koog 圖像處理範例 ===」,並加上一個換行符號
整體來說,這段程式碼示範如何初始化 OpenAI 的 LLM 客戶端及圖像分析工具,並在終端或控制台打印一段提示文字。
=== 批次圖片比較 ===
這兩張圖片風格、色調和主題截然不同:
1. 風格:
- 第一張:自然風格,拍攝手法偏向風景攝影,強調自然和寧靜。
- 第二張:數位風格,是程式碼截圖,偏技術和現代感。
2. 色調:
- 第一張:色彩明亮以綠色系為主,有自然光的柔和感。
- 第二張:主色調是深色背景搭配多色程式語言關鍵字高亮(紫、橘、綠等)。
3. 主題:
- 第一張:主題是自然環境與孤獨的木屋,帶有恬靜和放鬆氛圍。
- 第二張:主題是程式碼範例與技術展示,聚焦於OpenAI和圖片分析。
總結:第一張偏向自然與風景攝影,美感和情緒渲染強烈;第二張則是數位技術類,比較理性、冷靜和功能性。
| 功能特色 | 簡化語法 (image()) | 
Attachment API | 
|---|---|---|
| 使用難度 | 簡單直觀 | 稍微複雜 | 
| 參數控制 | 基本 | 完整控制 | 
| 圖像來源 | URL + 本地檔案 | URL + Bytes + Base64 + 文字 | 
| 批次處理 | 需要多次調用 | 原生支援 | 
| 檔案資訊 | 自動推斷 | 可指定格式、檔名等 | 
| 適用場景 | 快速原型開發 | 生產環境應用 | 
image() 語法在本篇的範例中,為了簡單方便,我們並沒有考慮到使用者上傳圖片時可能遇到的各種錯誤情況。在正式的程式碼中,建議一併考慮並實作以下錯誤處理
今天我們學習了 Koog 框架的圖像處理能力,掌握了
image() 語法進行圖片描述和文字提取(OCR)圖像處理為 AI 應用開啟了全新的可能性。從簡單的照片描述到複雜的文件分析,從教育輔助到商業自動化,視覺 AI 正在改變我們與數位內容互動的方式
掌握了圖像處理能力後,下一篇文章我們將探索 Koog 框架的另一個重要多媒體處理領域:文件處理。我們將學習如何讓 AI 讀懂 PDF、Word 文檔等各種文件格式,並實現 AI 處理文件摘要、關鍵資訊提取等功能。這對於處理商業報告、學術論文、技術文檔等場景將非常有用
圖片來源:www.pexels.com 以及 AI 產生